/**
 * CS543 HW4 Joseph Meyer
 */
package edu.src.hardware;

import java.util.ArrayList;
import java.util.List;

import edu.src.paging.Page;

/**
 * Singleton.<br>
 * The representation of the physical memory of the system. 
 * The page table will keep track of what is loading into physical memory.
 * when the page table faults, that means the entry is missing from physical memory, and must be
 * loading from the <code>BackingStore</code>.
 * <p>
 * The Size of this class must be switchable. 64K or 32K
 * 
 * @author meyer
 */
public class PhysicalMemory {
	
	//for 64K or 32K, this valueList will have different lengths
	private List<PhysicalMemoryFrame> frames;
	private MemorySize memSize;
	private static PhysicalMemory instance;
	private int frameCounter;
	private int pageSize = 256;

	/**
	 * The private Constructor.
	 * @param size - Default is 64K, but should be configurable for 32K
	 */
	private PhysicalMemory() {
		frames = new ArrayList<>(pageSize);
		clearPhysicalMemory();
		memSize = MemorySize.K64;
		frameCounter = 0;
	}
	
	/**
	 * Singleton accessor
	 * @return instance - PhysicalMemory
	 */
	public static PhysicalMemory getInstance() {
		if (instance == null) {
			instance = new PhysicalMemory();
		}
		return instance;
	}

	/**
	 * @param b byte to insert
	 * @param physicalAddress - use translateToPhysicalAddress to acquire this
	 */
	public int addByteToPhysicalMemory(byte b, int offset) {
		
		PhysicalMemoryFrame f = frames.get(frameCounter);
		
		offset = offset % pageSize;
		
		f.addToList(b, offset);
		
		int physicalAddress = (frameCounter * pageSize) + offset;
		
		frameCounter++;
		if (frameCounter == (pageSize-1)) {
			frameCounter = 0;
		}
		
		return physicalAddress;
	}
	
	/**
	 * When we are low on memory, check if the frame being accessed
	 * is available.
	 * 
	 * @param offset The offset to travel into the frame
	 * @return boolean. true if the location is available
	 */
	public boolean isFrameAvailable(int offset) {
		PhysicalMemoryFrame f = frames.get(frameCounter);
		Byte b = f.getFromList(offset);
		return b == null;
	}
	
	/**
	 * At this point, we know the frame we want is full.<br>
	 * Find the Least Recently Used item in the Physical
	 * Memory and replace this item with that one.
	 * 
	 * @return int - the physical address of the victim
	 */
	public int findVictim() {
		ByteEntry oldest = null;
		long oldestTime = Long.MAX_VALUE;
		int frameNumber = 0;
		for (PhysicalMemoryFrame frame : frames) {
			ByteEntry be = frame.getOldestEntry();
			if (be != null) {
				if (be.getTimestamp() < oldestTime) {
					oldestTime = be.getTimestamp();
					oldest = be;
				}
			}
			frameNumber++;
		}
		//oldest is the Least Recently Used item in physical memory this is the victim to remove.
//		System.out.println("Not enough physical memory. Removing least recently used entry.");
		int physicalAddress = (frameNumber * pageSize) + oldest.getIndex();
		return physicalAddress;
	}
	
	/**
	 * given a page and offset get the byte from physical memory. 
	 * 
	 * page will be multiplied by 256
	 * then the offset is added to that product.
	 * 
	 * @param page -  int - between 0 and 255
	 * @param offset - int - between 0 and 255
	 */
	public Byte getByteFromPhysicalMemory(Page p) {
		return getByteFromPhysicalMemory(p.getPhysicalMemoryAddress());
	}
	
	/**
	 * @param physicalAddress
	 * @return Byte - value at the address.
	 */
	public Byte getByteFromPhysicalMemory(int physicalAddress) {
		int offset = physicalAddress % pageSize;
		int frameNo = ((physicalAddress - offset) / (pageSize));
		PhysicalMemoryFrame f = frames.get(frameNo);
		Byte b = f.getFromList(offset);
		return b;
	}
	
	/**
	 * Used to reboot the physical memory
	 */
	public void clearPhysicalMemory() {
		frameCounter = 0;
		for (int i = 0; i < pageSize; i++) {
			frames.add(new PhysicalMemoryFrame());
		}
	}
	
	/**
	 * @return the memSize
	 */
	public MemorySize getMemSize() {
		return memSize;
	}

	/**
	 * @return the pageSize- 128 or 256
	 */
	public int getPageSize() {
		return pageSize;
	}

	/**
	 * Also resets the frames / pages
	 * @param memSize the memSize to set
	 */
	public void setMemSize(MemorySize memSize) {
		this.memSize = memSize;
		if (memSize.equals(MemorySize.K32)) {
			pageSize = 128;
		} else {
			pageSize = 256;
		}
		
		frames = new ArrayList<>(pageSize);
		clearPhysicalMemory();
	}

}
